/**
 * AGNTorus.java
 */

package agngui.agnmodel;
import java.util.ArrayList;
import java.util.List;

import javax.media.j3d.Appearance;
import javax.media.j3d.Geometry;
import javax.media.j3d.GeometryArray;
import javax.media.j3d.Material;
import javax.media.j3d.QuadArray;
import javax.media.j3d.Transform3D;
import javax.vecmath.AxisAngle4d;
import javax.vecmath.Color3f;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector3f;

/**
 * Manages the properties of the torus scattering region.
 */
public class AGNTorus extends AGNObject {

    /**
     * AGNTorus
     * 
     * INitializes the torus and adds the geometry to the system.
     *
     * @param ri     The torus inner radius
     * @param ro     The torus outer radius
     * @param ha     The torus half angle in degrees
     * @param facets Number of facets for the cross-section circle
     * @param n      Number of facets for the cross-section rotation
     */
    public AGNTorus( double ri, double ro, double ha, int facets, int n, AGNModelGeometry modelGeometry ) {
        super( AGNModelGeometry.SCATTERTORUS, modelGeometry );

        this.Ri     = ri;
        this.Ro     = ro;
        this.ha     = ha;
        this.facets = facets;
        this.n      = n;

        geometry = createGeometry();
        setGeometry( geometry[0] );

        setTransparency(0.75f);
		Appearance app = getAppearance();
		Material mat = app.getMaterial();
		mat.setDiffuseColor(new Color3f(1.0f, 0, 0));
		mat.setAmbientColor(new Color3f(0.5f, 0, 0));
		app.setMaterial(mat);
		setAppearance(app);

        transformGroup.addChild( this );
    }

    /**
     * generatePoints
     *
     * Generates a set of points in no useful order.
     *
     * @param ri     The torus inner radius.
     * @param ro     The torus outer radius. 
     * @param ha     The torus half-angle in radians
     * @param facets Number of faces around the conventional v parameter (about x-axis)
     * @param n      The number of faces around the convevntional u parameter (about z-axis)
     * @return The generated set of point
     */
    private Point3d[] generatePoints( double ri, double ro, double ha, int facets, int n ) {
        double dTheta     = (2 * Math.PI) / n;
        double phi        = 0;

        double a   = (ro - ri) / 2;
        double c   = ri + a;
        double avg = (ro + ri) / 2;
        double halfSemimajorAxis = (ro - ri) / 2;
        double halfSemiminorAxis = avg * Math.tan( (Math.PI / 2) - ha );

        ArrayList<Point3d> verts = new ArrayList<Point3d>();
        float c_length = (float)(c);
        List<Vector3f> normals = new ArrayList<Vector3f>();
        Vector3f c_vector = null;
        
        // Create initial ellipse
        for( int i = 0; i < facets; ++i ) {
            double dPhi = (2 * Math.PI) / facets;
            double x    = halfSemimajorAxis * Math.cos( phi ) + c;
            double z    = halfSemiminorAxis * Math.sin( phi );

            Point3d the_point = new Point3d( x, 0.0, z );
            
            if(i == 0) {
            	c_vector = new Vector3f(the_point);
            	c_vector.normalize();
            	c_vector.scale(c_length);
            }
            
            Vector3f v_p = new Vector3f(the_point);
            Vector3f norm = new Vector3f();
            norm.sub(v_p, c_vector);
            norm.normalize();
            normals.add(norm);

            verts.add( the_point );
            phi += dPhi;
        }

        //sweep the circle around and create the rest of the torus
        for( double theta = dTheta; theta <= (2* Math.PI); theta += dTheta ) {
            for( int i = 0; i < facets; ++i ) {
                Point3d p0 = verts.get( i );
                Point3d p1 = new Point3d( (p0.x * Math.cos( theta ) - p0.y * Math.sin( theta )),
                						  (p0.y * Math.cos( theta ) + p0.x * Math.sin( theta )),
                						  p0.z );
                if(i == 0) {
                	c_vector = new Vector3f(p1);
                	c_vector.normalize();
                	c_vector.scale(c_length);
                }
                
                Vector3f v_p = new Vector3f(p1);
                Vector3f norm = new Vector3f();
                norm.sub(v_p, c_vector);
                norm.normalize();
                normals.add(norm);

                verts.add( p1 );
            }
        }

        point_array = verts.toArray(new Point3d[0]);
        norm_array = normals.toArray(new Vector3f[0]);
		
        return verts.toArray( new Point3d[0] );

    }

    /**
     * generateIndexedPoints
     * 
     * Reindexes the set of points generated by generatePoints() to be rendered
     * as quads (every four points represents a quadrilateral).
     *
     * @param points The set of Point3d objects generated by generatePoints
     * @param facets The number of faces around the conventional v parameter (about x-axis)
     * @return a set of points defining the torus, ordered such that they represent quads
     */
    private Point3d[] generateIndexedPoints( Point3d[] points, int facets ) {
        List<Point3d> newPoints = new ArrayList<Point3d>();
        List<Vector3f> normals = new ArrayList<Vector3f>();
        
		int i0 = 0, i1 = 0, i2 = 0, i3 = 0;
        for(int ring = 0; ring < n; ring++) {
        	int next_ring = (ring + 1) % n;
        	for(int point_on_ring = 0; point_on_ring < facets; point_on_ring++) {
        		int next_point_on_ring = (point_on_ring + 1) % facets;
        
        		i0 = ring*facets + point_on_ring;
        		i1 = ring*facets + next_point_on_ring;
        		i2 = next_ring*facets + next_point_on_ring;
        		i3 = next_ring*facets + point_on_ring;
        		
        		newPoints.add(points[i0]);
        		newPoints.add(points[i3]);
        		newPoints.add(points[i2]);
        		newPoints.add(points[i1]);
        		
        		normals.add(norm_array[i0]);
        		normals.add(norm_array[i3]);
        		normals.add(norm_array[i2]);
        		normals.add(norm_array[i1]);
        	}
        }
        
        norm_array = normals.toArray(new Vector3f[0]);
        point_array = newPoints.toArray(new Point3d[0]);
        return newPoints.toArray( new Point3d[0] );
    }

    /**
     * createGeometry
     * 
     * Creates the torus geometry.
     *
     * @return a reference to the generated Geometry object.
     */
    protected Geometry[] createGeometry() {
        double ha = (this.ha * (Math.PI) / 180.0);

        Point3d points[] = generatePoints( Ri, Ro, ha, facets, n );
        Point3d coords[] = generateIndexedPoints( points, facets );

        QuadArray quads[] = new QuadArray[1];
        quads[0] = new QuadArray( coords.length, GeometryArray.COORDINATES | GeometryArray.NORMALS ); 
        quads[0].setCoordinates( 0, coords );
        quads[0].setNormals(0, norm_array);
		
		generateHandles();

        return quads;
    }
	
    /**
     * generateHandles
     *
     * Generates the torus-specific HUD handles based on the current state of the 
     * internal variables.
     */
    public void generateHandles() {
        Point3d  points[]           = new Point3d[5];
        Vector3d handleAxes         = new Vector3d( 1.0, 0.0, 0.0 );
        Vector3d anHandleAxis           = new Vector3d( 0.0, 1.0, 0.0 );
        Vector3d rotHandleAxisPolar     = new Vector3d( 1.0, 0.0, 0.0 );
        Vector3d rotHandleAxisAzimuthal = new Vector3d( 0.0, 0.0, 1.0 );
        AxisAngle4d polarRotation       = new AxisAngle4d();
        Transform3D axisTransform       = new Transform3D();
        Transform3D objectTransform;
        double xA, zA;
        double radHa       = ha * (Math.PI / 180);
        double rotDistance = Ro * 1.5;
        double rotRadius   = Ri / 1.5;
        double recentPolarValue     = (handles == null) ? 0.0 : handles[3].getRecentValue();
        double recentAzimuthalValue = (handles == null) ? 0.0 : handles[4].getRecentValue();

        if( handles == null ) {
            handles = new AGNParameterHandle[points.length];
        } else {
            // Transform polar axis according to azimuthal angle
            polarRotation.set( rotHandleAxisAzimuthal, recentAzimuthalValue );
            axisTransform.setRotation( polarRotation );
        }

        objectTransform = getFullTransform();

        points[0] = new Point3d( Ri, 0.0, 0.0 );
        points[1] = new Point3d( Ro, 0.0, 0.0 );

        // Angular handle
        xA = (Ro + Ri) / 2;
        zA = xA * Math.tan( (Math.PI / 2) - radHa );
        points[2] = new Point3d( xA, 0.0, zA );

        // Polar rotational handle
        points[3] = new Point3d( rotDistance, 0.0, rotRadius );
        points[3].scale( SCALE_FACTOR );

        // Azimuthal rotational handle
        points[4] = new Point3d( rotRadius, 0.0, rotDistance );
        points[4].scale( SCALE_FACTOR );

        objectTransform.transform( points[0] );
        objectTransform.transform( points[1] );
        objectTransform.transform( points[2] );
        axisTransform.transform( points[3] );
        objectTransform.transform( handleAxes );
        objectTransform.transform( anHandleAxis );
        axisTransform.transform( rotHandleAxisPolar );

        handles[0] = new AGNLinearParameterHandle( this, points[0], handleAxes, ID_RI ); // Inner radius
        handles[1] = new AGNLinearParameterHandle( this, points[1], handleAxes, ID_RO ); // Outer radius
        handles[2] = new AGNAngularParameterHandle( this, points[2], anHandleAxis, ID_HA ); // Half angle
        handles[3] = new AGNRotationalParameterHandle( this, points[3], rotHandleAxisPolar, 
                                                             recentPolarValue, ID_ROT_POLAR );
        handles[4] = new AGNRotationalParameterHandle( this, points[4], rotHandleAxisAzimuthal, 
                                                             recentAzimuthalValue, ID_ROT_AZIMUTHAL );
    }
	
}
